﻿import "System.ss";
import "System.Console.ss";
import "System.Math.ss";
import "System.HTML.Layout.ss";

public namespace GUIDemo
{
	using System;
	using System.Console;
	using System.HTML;

	/// Provides information about the minimum, maximum and preferred size of a widget
	public class SizeHint
	{
		public Size PreferredSize = new Size(1,1);
		public Size MinimumSize = new Size(1,1);
		public Size MaximumSize = new Size(100000, 100000);
		public bool ExpandsVertically = false;
		public bool ExpandsHorizontally = false;
	}
	
	public class Size
	{
		public num Width;
		public num Height;
		
		public ctor( num width, num height )
		{
			this.Width = width;
			this.Height = height;
		}
	}

	public class Point
	{
		public num X;
		public num Y;
	
		public ctor( num x, num y )
		{
			this.X = x;
			this.Y = y;
		}
	}
	
	public interface ILayoutable
	{
		public abstract SizeHint GetWidthHint();
		public abstract SizeHint GetHeightHint(num width);
		public abstract bool HasHeightForWidth { get; }
		public abstract Size Size { get; set; }
		public abstract Point Position { get; set; }
		public abstract void SetGeometry( Point pos, Size size);
		public abstract void OnTreeJoin();
		public abstract void OnTreeLeave();		
	}
		
	public abstract class Layout : ILayoutable
	{
		private Widget widget;
		
		public ctor()
		{
		}

		public abstract SizeHint GetWidthHint();
		public abstract SizeHint GetHeightHint(num width);
		public abstract Size Size { get; set; }
		public abstract Point Position { get; set; }
		public abstract void SetGeometry( Point pos, Size size);
		
		public bool HasHeightForWidth
		{
			get
			{
				foreach( var item in this.Content )
				{
					if ( item.HasHeightForWidth )
					{
						return true;
					}
				}
				return false;
			}
		}
		
		public bool RequiresWidget
		{
			get
			{
				return false;
			}
		}
		
		public Layout ParentLayout { get; set; }
		
		public Widget Widget
		{
			get
			{
				return this.widget;
			}
			internal set
			{
				this.widget = value;

				var dom = this.widget.GetDOM();
				foreach( var child in this.Content )
				{
					if ( child is Widget )
					{
						var widget = child as Widget;
						dom.AppendChild( widget.GetDOM() );
						if ( this.widget.IsInTree )
						{
							widget.OnTreeJoin();
						}
					}
					else if ( child is Layout )
					{
						var layout = child as Layout;
						layout.Widget = this.widget;
					}
				}

				if ( this.ParentLayout == null )
				{
					this.widget.Size => this.UpdateSize;
				}
			}
		}

		public void OnTreeJoin()
		{
			foreach( var item in this.Content )
			{
				item.OnTreeJoin();
			}
		}
		
		public void OnTreeLeave()
		{
			foreach( var item in this.Content )
			{
				item.OnTreeJoin();
			}
		}
		
		public abstract list<ILayoutable> Content { get; }		
		
		public void UpdateSize( Size size )
		{
			if ( size == null ) { return; }
			
			var SizeHint sh = this.GetWidthHint();
			var w = Math.Max( sh.MinimumSize.Width, Math.Min( sh.MaximumSize.Width, size.Width ));
			sh = this.GetHeightHint( w );
			var h = Math.Max( sh.MinimumSize.Height, Math.Min( sh.MaximumSize.Height, size.Height ));
			this.Size = new Size(w,h);
		}
	}
	
	public abstract class Widget : ILayoutable
	{
		private Size size;
		private Point position;
		private Layout layout;
		
		public ctor()
		{
			// this.Size = new Size(0,0);
		}

		public void SetGeometry( Point pos, Size size )
		{
			this.Position = pos;
			this.Size = size;
		}

		public bool HasHeightForWidth
		{
			get
			{
				if ( this.Layout != null )
				{
					return this.Layout.HasHeightForWidth;
				}
				return false;
			}
		}
		
		public SizeHint GetWidthHint()
		{
			if ( this.Layout != null )
			{
				return this.Layout.GetWidthHint();
			}
			return new SizeHint();
		}

		public SizeHint GetHeightHint(num width)
		{
			if ( this.Layout != null )
			{
				return this.Layout.GetHeightHint(width);
			}
			return new SizeHint();
		}
		
		public abstract Node GetDOM();	
		
		public bool IsInTree
		{
			get
			{
				return this.Parent != null && this.Parent.IsInTree;
			}
		}
		
		public void OnTreeJoin()
		{
			if ( this.layout != null )
			{
				this.layout.OnTreeJoin();
			}
		}
		
		public void OnTreeLeave()
		{
			if ( this.layout != null )
			{
				this.layout.OnTreeLeave();
			}
		}
		
		public num Width
		{
			get
			{
				return this.size.Width;
			}
			set
			{
				this.Size = new Size( value, this.size.Height );
			}
		}

		public num Height
		{
			get
			{
				return this.size.Height;
			}
			set
			{
				this.Size = new Size( this.size.Width, value );
			}
		}
		
		public Size Size
		{
			get
			{
				return this.size;
			}
			set
			{
				this.size = value;
				var dom = this.GetDOM();
				dom.style.width = this.size.Width.ToString() + "px";
				dom.style.height = this.size.Height.ToString() + "px";			
			}				
		}
		
		public Point Position
		{
			get
			{
				return this.position;
			}
			set
			{
				this.position = value;
				var dom = this.GetDOM();
				dom.style.left = this.position.X.ToString() + "px";
				dom.style.top = this.position.Y.ToString() + "px";
			}
		}
		
		public Layout Layout
		{
			get
			{
				return this.layout;
			}
			set
			{
				this.layout = value;
				this.layout.Widget = this;
			}
		}
		
		public Widget Parent { get; internal set; }
	}

	public abstract class BoxLayout : Layout
	{
		public class Item
		{
			public ILayoutable Content;
			public SizeHint SizeHint;
			public Size Size;
			public Point Position;
		}
				
		internal list<Item> items = [];
		/// If null then no size hint has been computed so far
		internal SizeHint sizeHint;
		internal Size size = new Size(0,0);
		internal Point position = new Point(0,0);
		
		public ctor()
		{
		}
		
		public void AddWidget( Widget widget )
		{
			var item = new Item();
			item.Content = widget;
			this.items.Push(item);
		
			var dom = widget.GetDOM();
			dom.style.position = CSSPosition.absolute;
			dom.style.margin = "0px";
		
			// Layout is added to a widget already?
			if ( this.Widget != null )
			{
				// Append the new widget in the DOM
				this.Widget.GetDOM().AppendChild( widget.GetDOM() );
				
				// The parent widget is already visible in the browser
				if (this.Widget.IsInTree)
				{
					// Signal that the new widget is visible in the browswer, too
					widget.OnTreeJoin();
					// The layout has already layouted? -> repeat
					if ( this.size != null )
					{
						// Trigger a re-layout
						this.Size = this.Size;
					}
				}
			}
			else
			{
				this.sizeHint = null;
			}
		}

		public void AddLayout( Layout layout )
		{
			var item = new Item();
			item.Content = layout;
			this.items.Push(item);
			layout.ParentLayout = this;
			
			if ( this.Widget != null )
			{
				layout.Widget = this.Widget;
				// The layout has already layouted? -> repeat
				if ( this.size != null )
				{
					// Trigger a re-layout
					this.Size = this.Size;
				}
			}
			else
			{
				this.sizeHint = null;
			}
		}

		public override list<ILayoutable> Content
		{
			get
			{
				var list<ILayoutable> lst = [];
				foreach( var item in this.items )
				{
					lst.Push( item.Content );
				}
				return lst;
			}
		}
				
		public override Point Position
		{
			get
			{
				return this.position;
			}
			set
			{
				this.SetGeometry( value, this.Size );
			}
		}
		
		public override void SetGeometry( Point pos, Size size)
		{
			this.position = pos;
			this.Size = size;
		}
	}
	
	public class VBoxLayout : BoxLayout
	{		
		public ctor()
		{
		}

		protected override SizeHint GetWidthHint()
		{			
			this.sizeHint = new SizeHint();

			var minSize = new Size(0,0);
			var maxSize = new Size(100000, 100000);
			var prefSize = new Size(0,0);
			foreach( var item in this.items )
			{
				var SizeHint sh = item.Content.GetWidthHint();
				item.SizeHint = sh;
				
				prefSize.Width = Math.Max( sh.PreferredSize.Width, prefSize.Width );
				minSize.Width = Math.Max( sh.MinimumSize.Width, minSize.Width );
				maxSize.Width = Math.Max( sh.MaximumSize.Width, maxSize.Width );
				if ( sh.ExpandsVertically )
				{
					this.sizeHint.ExpandsVertically = true;
				}
				if ( sh.ExpandsHorizontally )
				{
					this.sizeHint.ExpandsHorizontally = true;
				}
			}
			this.sizeHint.PreferredSize = prefSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.MinimumSize = minSize;
		
			return this.sizeHint;
		}
		
		protected override SizeHint GetHeightHint( num width )
		{
			if ( this.sizeHint == null )
			{
				this.GetWidthHint();
			}
			
			var minSize = new Size(0,0);
			var maxSize = new Size(100000, 100000);
			var prefSize = new Size(0,0);
			foreach( var item in this.items )
			{
				var w = item.SizeHint.PreferredSize.Width;
				// We can grow the content horizontally
				if ( w < width && item.SizeHint.ExpandsHorizontally )
				{
					w = Math.Min( width, item.SizeHint.MaximumSize.Width );
				}
				// We must shrink the content horizontally?
				else if ( w > width )
				{
					w = Math.Max( width, item.SizeHint.MinimumSize.Width );
				}
				// The width is fixed. Now compute the height
				var SizeHint sh = item.Content.GetHeightHint( w );
				// Remember the result
				item.SizeHint = sh;
				
				prefSize.Height += sh.PreferredSize.Height;
				prefSize.Width = Math.Max( sh.PreferredSize.Width, prefSize.Width );
				minSize.Height += sh.MinimumSize.Height;
				minSize.Width = Math.Max( sh.MinimumSize.Width, minSize.Width );
				maxSize.Height += sh.MaximumSize.Height;
				maxSize.Width = Math.Max( sh.MaximumSize.Width, maxSize.Width );
			}

			this.sizeHint.PreferredSize = prefSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.MinimumSize = minSize;
		
			return this.sizeHint;
		}
		
		protected override Size Size
		{
			get
			{
				return this.size;
			}
			set
			{
				this.size = value;
				if ( !this.Widget.IsInTree )
				{
					return;
				}
							
				var expands = 0;
				foreach( var item in this.items )
				{
					// Could the content grow vertically?
					if ( item.SizeHint.ExpandsVertically && item.SizeHint.MaximumSize.Height > item.SizeHint.PreferredSize.Height )
					{
						expands++;
					}
					item.Size = item.SizeHint.PreferredSize;
				}

				// Distribute the remaining space  (if possible)
				var overlap = this.size.Height - this.sizeHint.PreferredSize.Height;
				while( overlap != 0 && expands > 0 )
				{
					var dy = Math.Max( Math.Floor( overlap / expands), 1 );
					if ( overlap < 0 )
					{
						dy = Math.Min( Math.Ceil( overlap / expands), -1 );
					}
					expands = 0;
					foreach( var item in this.items )
					{
						if ( overlap == 0 )
						{
							break;
						}
						if ( item.SizeHint.ExpandsVertically )
						{
							var h = item.Size.Height;
							item.Size = new Size(item.Size.Width, Math.Max( Math.Min( h + dy, item.SizeHint.MaximumSize.Height ), item.SizeHint.MinimumSize.Height ));							
							if ( overlap > 0 && item.SizeHint.MaximumSize.Height > item.Size.Height )
							{
								expands++;
							}
							else if ( overlap < 0 && (item.SizeHint.MinimumSize.Height) < (item.Size.Height) )
							{
								expands++;
							}
							overlap -= item.Size.Height - h;
						}
					}
				}
				
				// Position the elements
				var y = 0;
				foreach( var item in this.items )
				{
					item.Position = new Point( this.position.X, this.position.Y + y );
					//WriteLine("x=" + item.Position.X.ToString() + " y=" + item.Position.Y.ToString() + " w=" + item.Size.Width.ToString() + " h=" + item.Size.Height.ToString() );
					item.Content.SetGeometry( item.Position, item.Size );
					y += item.Size.Height;
				}
			}
		}
	}

	public class HBoxLayout : BoxLayout
	{		
		public ctor()
		{
		}
						
		protected override SizeHint GetWidthHint()
		{
			this.sizeHint = new SizeHint();

			var minSize = new Size(0,0);
			var maxSize = new Size(100000, 100000);
			var prefSize = new Size(0,0);
			foreach( var item in this.items )
			{
				var SizeHint sh = item.Content.GetWidthHint();
				item.SizeHint = sh;
				
				prefSize.Width += sh.PreferredSize.Width;
				prefSize.Height = Math.Max( sh.PreferredSize.Height, prefSize.Height );
				minSize.Width += sh.MinimumSize.Width;
				minSize.Height = Math.Max( sh.MinimumSize.Height, minSize.Height );
				maxSize.Width += sh.MaximumSize.Width;
				maxSize.Height = Math.Max( sh.MaximumSize.Height, maxSize.Height );
				if ( sh.ExpandsVertically )
				{
					this.sizeHint.ExpandsVertically = true;
				}
				if ( sh.ExpandsHorizontally )
				{
					this.sizeHint.ExpandsHorizontally = true;
				}
			}
			this.sizeHint.PreferredSize = prefSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.MinimumSize = minSize;
		
			return this.sizeHint;
		}
		
		protected override SizeHint GetHeightHint( num width )
		{
			if ( this.sizeHint == null )
			{
				this.GetWidthHint();
			}

			var expands = 0;
			foreach( var item in this.items )
			{
				// Could the content grow vertically?
				if ( item.SizeHint.ExpandsHorizontally && item.SizeHint.MaximumSize.Width > item.SizeHint.PreferredSize.Width )
				{
					expands++;
				}
				item.Size = item.SizeHint.PreferredSize;
			}
			
			// Distribute the remaining space  (if possible)
			var overlap = width - this.sizeHint.PreferredSize.Width;
			while( overlap != 0 && expands > 0 )
			{
				var dx = Math.Max( Math.Floor( overlap / expands), 1 );
				if ( overlap < 0 )
				{
					dx = Math.Min( Math.Ceil( overlap / expands), -1 );
				}
				expands = 0;
				foreach( var item in this.items )
				{
					if ( overlap == 0 )
					{
						break;
					}
					if ( item.SizeHint.ExpandsHorizontally || overlap < 0 )
					{
						var w = item.Size.Width;
						item.Size = new Size( Math.Max( Math.Min( w + dx, item.SizeHint.MaximumSize.Width ), item.SizeHint.MinimumSize.Width), item.Size.Height);
						if ( overlap > 0 && item.SizeHint.MaximumSize.Width > item.Size.Width )
						{
							expands++;
						}
						if ( overlap < 0 && (item.SizeHint.MinimumSize.Width) < (item.Size.Width) )
						{
							expands++;
						}
						overlap -= item.Size.Width - w;
					}
				}
			}

			var minSize = new Size(0,0);
			var maxSize = new Size(100000, 100000);
			var prefSize = new Size(0,0);
			foreach( var item in this.items )
			{
				var SizeHint sh = item.Content.GetHeightHint( item.SizeHint.PreferredSize.Width );
				item.SizeHint = sh;
				
				prefSize.Width += sh.PreferredSize.Width;
				prefSize.Height = Math.Max( sh.PreferredSize.Height, prefSize.Height );
				minSize.Width += sh.MinimumSize.Width;
				minSize.Height = Math.Max( sh.MinimumSize.Height, minSize.Height );
				maxSize.Width += sh.MaximumSize.Width;
				maxSize.Height = Math.Max( sh.MaximumSize.Height, maxSize.Height );
			}
			this.sizeHint.PreferredSize = prefSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.MinimumSize = minSize;
			
			return this.sizeHint;
		}

		protected override Size Size
		{
			get
			{
				return this.size;
			}
			set
			{
				this.size = value;
				if ( !this.Widget.IsInTree )
				{
					return;
				}

				foreach( var item in this.items )
				{
					var h = item.SizeHint.PreferredSize.Height;
					// We can grow the content vertically
					if ( h < this.size.Height && item.SizeHint.ExpandsVertically )
					{
						h = Math.Min( this.size.Height, item.SizeHint.MaximumSize.Height );
					}
					// We must shrink the content vertically?
					else if ( h > this.size.Height )
					{
						h = Math.Max( this.size.Height, item.SizeHint.MinimumSize.Height );
					}
					item.Size = new Size( item.Size.Width, h );
				}
				
				// Position the elements
				var x = 0;
				foreach( var item in this.items )
				{
					item.Position = new Point( this.position.X + x, this.position.Y );
					//WriteLine("x=" + item.Position.X.ToString() + " y=" + item.Position.Y.ToString() + " w=" + item.Size.Width.ToString() + " h=" + item.Size.Height.ToString() );
					item.Content.SetGeometry( item.Position, item.Size );
					x += item.Size.Width;
				}
			}
		}
	}

	public class DockLayout : Layout
	{				
		private class Item
		{
			public ctor(ILayoutable content)
			{
				this.Content = content;
			}
			
			public ILayoutable Content;
			public SizeHint SizeHint;
			public Size Size;
			public Point Position;
		}
				
		/// If null then no size hint has been computed so far
		private SizeHint sizeHint;
		private Size size = new Size(0,0);
		private Point position = new Point(0,0);
		private Item left;
		private Item right;
		private Item top;
		private Item bottom;
		private Item center;

		public ctor()
		{
		}

		public ILayoutable LeftContent
		{
			get
			{
				return this.left.Content;
			}
			set
			{
				this.left = new Item(value);
				this.PostSetContent(this.left);
			}
		}
		
		public ILayoutable RightContent
		{
			get
			{
				return this.right.Content;
			}
			set
			{
				this.right = new Item(value);
				this.PostSetContent(this.right);
			}
		}

		public ILayoutable TopContent
		{
			get
			{
				return this.top.Content;
			}
			set
			{
				this.top = new Item(value);
				this.PostSetContent(this.top);
			}
		}

		public ILayoutable BottomContent
		{
			get
			{
				return this.bottom.Content;
			}
			set
			{
				this.bottom = new Item(value);
				this.PostSetContent(this.bottom);
			}
		}

		public ILayoutable CenterContent
		{
			get
			{
				return this.center.Content;
			}
			set
			{
				if ( value is Layout && (value as Layout).RequiresWidget )
				{
					var panel = new Panel();
					panel.Layout = value as Layout;
					this.center = new Item(panel);
				}
				else
				{
					this.center = new Item(value);
				}
				this.PostSetContent(this.center);
			}
		}

		private void PostSetContent(Item item)
		{
			if ( item.Content is Layout )
			{
				var layout = item.Content as Layout;
				if ( this.Widget != null )
				{
					layout.Widget = this.Widget;
					// The layout has already layouted? -> repeat
					if ( this.size != null )
					{
						// Trigger a re-layout
						this.Size = this.Size;
					}
				}
				else
				{
					this.sizeHint = null;
				}
			}
			else
			{
				var widget = item.Content as Widget;
				
				var dom = widget.GetDOM();
				dom.style.position = CSSPosition.absolute;
				dom.style.margin = "0px";

				// Layout is added to a widget already?
				if ( this.Widget != null )
				{
					// Append the new widget in the DOM
					this.Widget.GetDOM().AppendChild( widget.GetDOM() );
				
					// The parent widget is already visible in the browser
					if (this.Widget.IsInTree)
					{
						// Signal that the new widget is visible in the browswer, too
						widget.OnTreeJoin();
						// The layout has already layouted? -> repeat
						if ( this.size != null )
						{
							// Trigger a re-layout
							this.Size = this.Size;
						}
					}
				}
				else
				{
					this.sizeHint = null;
				}
			}
		}
		
		public override list<ILayoutable> Content
		{
			get
			{
				var list<ILayoutable> lst = [];
				if ( this.left != null )
				{
					lst.Push( this.left.Content );
				}
				if ( this.right != null )
				{
					lst.Push( this.right.Content );
				}
				if ( this.top != null )
				{
					lst.Push( this.top.Content );
				}
				if ( this.bottom != null )
				{
					lst.Push( this.bottom.Content );
				}
				if ( this.center != null )
				{
					lst.Push( this.center.Content );
				}	
				return lst;
			}
		}
				
		public override SizeHint GetWidthHint()
		{
			this.sizeHint = new SizeHint();
						
			var Size minSize = new Size(0,0);
			var Size maxSize = new Size(100000,100000);
			var Size prefSize = new Size(0,0);
			
			var func = function( Item item ) {
				if ( item != null )
				{
					item.SizeHint = item.Content.GetWidthHint();
					if ( item.SizeHint.ExpandsVertically ) { this.sizeHint.ExpandsVertically = true; }
					if ( item.SizeHint.ExpandsHorizontally ) { this.sizeHint.ExpandsHorizontally = true; }
					minSize.Width += item.SizeHint.MinimumSize.Width;
					maxSize.Width += item.SizeHint.MaximumSize.Width;
					prefSize.Width += item.SizeHint.PreferredSize.Width;
				}
			};
			func( this.left );
			func( this.center );
			func( this.right );

			func = function( Item item ) {
				if ( item != null )
				{
					item.SizeHint = item.Content.GetWidthHint();
					if ( item.SizeHint.ExpandsVertically ) { this.sizeHint.ExpandsVertically = true; }
					if ( item.SizeHint.ExpandsHorizontally ) { this.sizeHint.ExpandsHorizontally = true; }
					minSize.Width = Math.Max(minSize.Width, item.SizeHint.MinimumSize.Width);
					maxSize.Width = Math.Min(maxSize.Width, item.SizeHint.MaximumSize.Width);
					prefSize.Width = Math.Max(prefSize.Width, item.SizeHint.PreferredSize.Width);
				}
			};			
			func( this.top );
			func( this.bottom );
			
			this.sizeHint.MinimumSize = minSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.PreferredSize = prefSize;
			
			return this.sizeHint;
		}
		
		public override SizeHint GetHeightHint( num width )
		{
			if ( this.sizeHint == null )
			{
				this.GetWidthHint();
			}
			
			var Size minSize = new Size(this.sizeHint.MinimumSize.Width,0);
			var Size maxSize = new Size(this.sizeHint.MaximumSize.Width,100000);
			var Size prefSize = new Size(this.sizeHint.PreferredSize.Width,0);
			if ( this.top != null )
			{
				this.top.SizeHint = this.top.Content.GetHeightHint(width);
				minSize.Height += this.top.SizeHint.MinimumSize.Height;
				maxSize.Height += this.top.SizeHint.MaximumSize.Height;
				prefSize.Height += this.top.SizeHint.PreferredSize.Height;
			}
			if ( this.bottom != null )
			{
				this.bottom.SizeHint = this.bottom.Content.GetHeightHint(width);
				minSize.Height += this.bottom.SizeHint.MinimumSize.Height;
				maxSize.Height += this.bottom.SizeHint.MaximumSize.Height;
				prefSize.Height += this.bottom.SizeHint.PreferredSize.Height;
			}
			
			// How much width are preferred for left and right?
			var leftw = 0;
			if ( this.left != null )
			{
				leftw = this.left.SizeHint.PreferredSize.Width;
			}
			var rightw = 0;
			if ( this.right != null )
			{
				rightw = this.right.SizeHint.PreferredSize.Width;				
			}
			
			var prefh = 0;
			var minh = 0;
			var maxh = 100000;
			var remain = width - leftw - rightw;
			// Try to fill the remaining space with the center widget						
			if ( this.center != null )
			{
				var w = Math.Max( this.center.SizeHint.MinimumSize.Width, Math.Min( this.center.SizeHint.MaximumSize.Width, remain ));
				this.center.SizeHint = this.center.Content.GetHeightHint( w );
				prefh = this.center.SizeHint.PreferredSize.Height;
				minh = this.center.SizeHint.MinimumSize.Height;
				maxh = this.center.SizeHint.MaximumSize.Height;				
				remain -= w;
			}
			// While some space is left or missing, fix it by changing the left and right width
			while( remain != 0 )
			{
				var part = Math.Max( 1, Math.Floor( remain / 2 ) );
				if ( remain < 0 )
				{
					part = Math.Min( -1, Math.Ceil( remain / 2 ) );
				}
				if ( this.left != null )
				{
					var old = leftw;
					leftw = Math.Max( Math.Min( this.left.SizeHint.MaximumSize.Width, leftw + part ), this.left.SizeHint.MinimumSize.Width);
					remain -= (leftw - old);
				}
				if ( this.right != null && remain != 0 )
				{
					var old = rightw;
					rightw = Math.Max( Math.Min( this.right.SizeHint.MaximumSize.Width, rightw + part ), this.right.SizeHint.MinimumSize.Width);
					remain -= (rightw - old);
				}
			}
			
			if ( this.left != null )
			{
				this.left.SizeHint = this.left.Content.GetHeightHint( leftw );				
				prefh = Math.Max( prefh, this.left.SizeHint.PreferredSize.Height);
				minh = Math.Max( minh, this.left.SizeHint.MinimumSize.Height);
				maxh = Math.Min( maxh, this.left.SizeHint.MaximumSize.Height);
			}
			if ( this.right != null )
			{
				this.right.SizeHint = this.right.Content.GetHeightHint( rightw );
				prefh = Math.Max( prefh, this.right.SizeHint.PreferredSize.Height);
				minh = Math.Max( minh, this.right.SizeHint.MinimumSize.Height);
				maxh = Math.Min( maxh, this.right.SizeHint.MaximumSize.Height);
			}
			
			minSize.Height += minh;
			maxSize.Height += maxh;
			prefSize.Height += prefh;

			this.sizeHint.MinimumSize = minSize;
			this.sizeHint.MaximumSize = maxSize;
			this.sizeHint.PreferredSize = prefSize;
			
			return this.sizeHint;
		}

		protected override Size Size
		{
			get
			{
				return this.size;
			}
			set
			{
				this.size = value;
				if ( !this.Widget.IsInTree )
				{
					return;
				}

				var dh = this.size.Height;
				var toph = 0;
				if ( this.top != null )
				{
					this.top.Size = new Size( this.top.SizeHint.PreferredSize.Width, this.top.SizeHint.PreferredSize.Height );
					toph = this.top.Size.Height;
					this.top.Position = new Point( 0, 0 );
					dh -= toph;
				}
				if ( this.bottom != null )
				{
					this.bottom.Size = new Size( this.bottom.SizeHint.PreferredSize.Width, this.bottom.SizeHint.PreferredSize.Height );
					dh -= this.bottom.Size.Height;
				}
				
				//  Distribute as much as possible towards the center
				if ( this.center != null && dh > this.center.SizeHint.PreferredSize.Height )
				{
					this.center.Size = new Size( this.center.SizeHint.PreferredSize.Width, Math.Min( dh, this.center.SizeHint.MaximumSize.Height) );
					dh -= this.center.Size.Height;
				}
				// Shrink the center as much as possible
				else if ( this.center != null )
				{
					var old = this.center.SizeHint.MinimumSize.Height;
					this.center.Size = new Size( this.center.SizeHint.PreferredSize.Width, Math.Max( dh, this.center.SizeHint.MinimumSize.Height) );
					dh -= this.center.Size.Height;					
				}
				
				// There is still some shrinking to do?
				if ( dh != 0 )
				{
					// How much do we have to distribute (minus the center)?
					dh = this.size.Height;
					if ( this.center != null ) { dh -= this.center.Size.Height; }
					
					while( dh > 0 )
					{
						var part = Math.Max( 1, dh / 2 );
						if ( this.top != null )
						{
							var old = toph;
							toph = Math.Min( this.top.SizeHint.MaximumSize.Height, toph + part );
							dh -= (toph - old);
							this.top.Size.Height = toph;
						}
						if ( this.bottom != null )
						{
							var old = this.bottom.Size.Height;
							this.bottom.Size.Height = Math.Min( this.bottom.SizeHint.MaximumSize.Height, old + part );
							dh -= (this.bottom.Size.Height - old);
						}
					}
				}
				
				// Position the elements
				if ( this.top != null )
				{
					var pos = new Point( this.position.X + this.top.Position.X, this.position.Y + this.top.Position.Y);
					this.top.Content.SetGeometry( pos, this.top.Size );
				}
				var bottomh = 0;
				if ( this.bottom != null )
				{
					this.bottom.Position = new Point( 0, this.size.Height - this.bottom.Size.Height );
					var pos = new Point( this.position.X + this.bottom.Position.X, this.position.Y + this.bottom.Position.Y);
					this.bottom.Content.SetGeometry( pos, this.bottom.Size );
					bottomh = this.bottom.Size.Height;
				}
				var leftw = 0;				
				if ( this.left != null )
				{
					this.left.Size = new Size( this.left.SizeHint.PreferredSize.Width, this.size.Height - toph - bottomh );
					this.left.Position = new Point( 0, toph );
					leftw = this.left.Size.Width;
					var pos = new Point( this.position.X + this.left.Position.X, this.position.Y + this.left.Position.Y);
					this.left.Content.SetGeometry( pos, this.left.Size );
				}
				if ( this.right != null )
				{
					this.right.Size = new Size( this.right.SizeHint.PreferredSize.Width, this.size.Height - toph - bottomh );
					this.right.Position = new Point( this.size.Width - this.right.Size.Width, toph );
					var pos = new Point( this.position.X + this.right.Position.X, this.position.Y + this.right.Position.Y);
					this.right.Content.SetGeometry( pos, this.right.Size );
				}
				if ( this.center != null )
				{
					this.center.Position = new Point( leftw, toph );
					var pos = new Point( this.position.X + this.center.Position.X, this.position.Y + this.center.Position.Y);
					this.center.Content.SetGeometry( pos, this.center.Size );
				}
			}
		}
		
		public override Point Position
		{
			get
			{
				return this.position;
			}
			set
			{
				this.SetGeometry( value, this.Size );
			}
		}
		
		public override void SetGeometry( Point pos, Size size)
		{
			this.position = pos;
			this.Size = size;
		}
	}
	
	public class FlowLayout : Layout
	{
		/// If null then no size hint has been computed so far
		private SizeHint sizeHint;
		private list<Item> items = [];
		
		private class Item
		{
			public Widget Widget;
			public SizeHint SizeHint;
		}
		
		public ctor()
		{
			this.sizeHint = new SizeHint();
			this.sizeHint.PreferredSize = new Size(10,10);
			this.sizeHint.ExpandsVertically = true;
			this.sizeHint.ExpandsHorizontally = true;
		}

		public void AddWidget( Widget widget )
		{
			var Item item = new Item();
			item.Widget = widget;
			this.items.Push( item);
			
			var dom = widget.GetDOM();
			dom.style.float = CSSFloat.left;
			dom.style.position = CSSPosition.relative;
			
			// Layout is added to a widget already?
			if ( this.Widget != null )
			{
				// Append the new widget in the DOM
				this.Widget.GetDOM().AppendChild( widget.GetDOM() );
				
				// The parent widget is already visible in the browser
				if (this.Widget.IsInTree)
				{
					// Signal that the new widget is visible in the browswer, too
					widget.OnTreeJoin();
					// The layout has already layouted? -> repeat
					if ( this.Size != null )
					{
						// Trigger a re-layout
						this.Size = this.Size;
					}
				}
			}
		}

			
		public override list<ILayoutable> Content
		{
			get
			{
				var list<ILayoutable> lst = [];
				foreach( var item in this.items )
				{
					lst.Push(item.Widget);
				}
				return lst;
			}
		}
		
		public override SizeHint GetWidthHint()
		{
			foreach( var item in this.items )
			{
				item.SizeHint = item.Widget.GetWidthHint();
			}
			
			return this.sizeHint;
		}
		
		public override SizeHint GetHeightHint(num width)
		{
			foreach( var item in this.items )
			{
				item.SizeHint = item.Widget.GetHeightHint(item.SizeHint.PreferredSize.Width);
			}

			var SizeHint sh = new SizeHint();
			sh.PreferredSize = new Size( width, this.sizeHint.PreferredSize.Height );
			sh.MinimumSize = this.sizeHint.MinimumSize;
			sh.MaximumSize = this.sizeHint.MaximumSize;
			sh.ExpandsVertically = this.sizeHint.ExpandsVertically;
			sh.ExpandsHorizontally = this.sizeHint.ExpandsHorizontally;
			return sh;
		}
		
		public override Size Size { get; set; }
		
		public override Point Position { get; set; }
		
		public override void SetGeometry( Point pos, Size size)
		{
			this.Position = pos;
			this.Size = size;
		}

		public override bool RequiresWidget
		{
			get
			{
				return true;
			}
		}
	}
	
	public class ToplevelWidget : Widget
	{
		private Node body;

		public ctor()
		{
			this.body = document.GetElementById("body");
			this.Size = new Size( window.InnerWidth, window.InnerHeight );
			window.Resize => this.OnWindowResize;
		}
				
		public override Node GetDOM()
		{
			return this.body;
		}
		
		private void OnWindowResize(Event e)
		{
			var Size s = new Size( window.InnerWidth, window.InnerHeight );
			this.Size = s;
		}
				
		public override bool IsInTree
		{
			get
			{
				return true;
			}
		}
	}
	
	public class HTMLWidget : Widget
	{
		private Node div;
		private Node html;
		private Size origSize;
		private SizeHint sizeHint;
		private bool hasHeightForWidth = true;
		
		public ctor( Node html )
		{
			this.sizeHint = new SizeHint();
			// this.div = <div style="position:absolute; border:none; margin:0px; padding:0px" />;
			this.Html = html;
		}
		
		public Node Html
		{
			get
			{
				return this.html;
			}
			set
			{
				if ( this.html != null )
				{
					this.div.RemoveChild( this.html );
				}
				this.html = value;
				this.div = value;
				//if ( this.html != null )
				//{
				//	this.div.AppendChild( this.html );
				//}
			}
		}

		public override bool HasHeightForWidth
		{
			get
			{
				return this.hasHeightForWidth;
			}
			//set
			//{
			//	this.hasHeightForWidth = value;
			//}
		}
				
		public override SizeHint GetWidthHint()
		{
			this.sizeHint.PreferredSize = new Size( this.origSize.Width, this.origSize.Height );
			
			return this.sizeHint;
		}
		
		public override SizeHint GetHeightHint(num width)
		{
			if ( this.hasHeightForWidth == true )
			{
				this.div.style.height = null;
			}
			this.div.style.width = width.ToString() + "px";
			this.sizeHint.PreferredSize = new Size( this.div.offsetWidth, this.div.offsetHeight );
			return this.sizeHint;
		}

		public override Node GetDOM()
		{
			return this.div;
		}
		
		protected override void OnTreeJoin()
		{
			super.OnTreeJoin();
			
			this.origSize = new Size( this.div.offsetWidth, this.div.offsetHeight );
		}

	}
	
	public class Panel : Widget
	{
		private Node dom;
		
		public ctor()
		{
			this.dom = <div />;
		}
		
		public override Node GetDOM()
		{
			return this.dom;
		}
	}
	
	if ( true )
	{
		var win = new ToplevelWidget();
		var layout = new DockLayout();
		var top = new HTMLWidget( <h1 style="background-color:blue">Das ist oben und hier ist mehr Platz</h1> );
		top.sizeHint.ExpandsHorizontally = true;
		layout.TopContent = top;
		var bottom = new HTMLWidget( <h2 style="background-color:green">Das ist unten und hier hat es auch Platz</h2> );
		bottom.sizeHint.ExpandsHorizontally = true;
		layout.BottomContent = bottom;
		var left = new HTMLWidget( <h2 style="background-color:yellow">Das ist links</h2> );
		left.sizeHint.ExpandsVertically = true;
		layout.LeftContent = left;	
		var right = new HTMLWidget( <h2 style="background-color:purple">Das ist rechts</h2> );
		right.sizeHint.ExpandsVertically = true;
		layout.RightContent = right;

		var clayout = new FlowLayout();
		var content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box One</div> );
		//content.HasHeightForWidth = false;
		clayout.AddWidget( content );
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Two</div> );
		//content.HasHeightForWidth = false;
		clayout.AddWidget( content );		
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Three</div> );
		//content.HasHeightForWidth = false;
		clayout.AddWidget( content );		
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Four</div> );
		//content.HasHeightForWidth = false;
		clayout.AddWidget( content );		
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Five</div> );
		//content.HasHeightForWidth = false;
		clayout.AddWidget( content );		
		layout.CenterContent = clayout;

		win.Layout = layout;
	}
	else if ( false )
	{
		var win = new ToplevelWidget();
		var layout = new FlowLayout();
		var content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box One</div> );
		//content.HasHeightForWidth = false;
		layout.AddWidget( content );
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Two</div> );
		//content.HasHeightForWidth = false;
		layout.AddWidget( content );		
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Three</div> );
		//content.HasHeightForWidth = false;
		layout.AddWidget( content );		
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Four</div> );
		//content.HasHeightForWidth = false;
		layout.AddWidget( content );		
		content = new HTMLWidget( <div style="width:100px; height:100px; margin:5px; background-color:red">Box Five</div> );
		//content.HasHeightForWidth = false;
		layout.AddWidget( content );		
		win.Layout = layout;		
	}
	else if ( false )
	{
		var win = new ToplevelWidget();
		var layout = new DockLayout();
		var top = new HTMLWidget( <h1 style="background-color:blue">Das ist oben und hier ist mehr Platz</h1> );
		top.sizeHint.ExpandsHorizontally = true;
		layout.TopContent = top;
		var bottom = new HTMLWidget( <h2 style="background-color:green">Das ist unten und hier hat es auch Platz</h2> );
		bottom.sizeHint.ExpandsHorizontally = true;
		layout.BottomContent = bottom;
		var left = new HTMLWidget( <h2 style="background-color:yellow">Das ist links</h2> );
		left.sizeHint.ExpandsVertically = true;
		layout.LeftContent = left;	
		var right = new HTMLWidget( <h2 style="background-color:red">Das ist rechts</h2> );
		right.sizeHint.ExpandsVertically = true;
		layout.RightContent = right;
		var center = new HTMLWidget( <h2 style="background-color:purple">Das ist der Kram inner Mitte welcher den meisten Platz hat</h2> );
		center.sizeHint.ExpandsHorizontally = true;
		center.sizeHint.ExpandsVertically = true;
		layout.CenterContent = center;
		win.Layout = layout;
	}
	else
	{
		var win = new ToplevelWidget();
		var layout = new VBoxLayout();
		var content = new HTMLWidget( <h1 style="background-color:blue">Hallo Welt</h1> );
		content.sizeHint.ExpandsHorizontally = true;
		layout.AddWidget( content );
		var content2 = new HTMLWidget( <h2 style="background-color:green">Alles nur Unsinn</h2> );
		content2.sizeHint.ExpandsVertically = true;
		layout.AddWidget( content2 );
		content2 = new HTMLWidget( <h2 style="background-color:yellow">Alles nur noch mehr Unsinn der garantiert nicht hier her passt</h2> );
		content2.sizeHint.ExpandsVertically = true;
		layout.AddWidget( content2 );	
		var layout2 = new HBoxLayout();
		var content3 = new HTMLWidget( <h2 style="background-color:red">Innere VBox</h2> );
		content3.sizeHint.ExpandsVertically = true;
		content3.sizeHint.ExpandsHorizontally = true;
		layout2.AddWidget( content3 );
		var content4 = new HTMLWidget( <h2 style="background-color:purple">Outer VBox</h2> );
		content4.sizeHint.ExpandsHorizontally = true;
		layout2.AddWidget( content4 );		
		layout.AddLayout(layout2);
		win.Layout = layout;
	}
}